﻿/*
   RxInputManager
   Copyright 2011 Jordan Neumeyer

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
   
   File: RxGamePadExt.cs 
 */
using System;
using System.Linq;
using System.Reactive.Linq;
using Microsoft.Xna.Framework.Input;
using System.Collections.Generic;

namespace RxInputManager.Extensions
{
    public static class RxGamePadExt
    {
        public static IObservable<GamePadDPad> ToDPad(this IObservable<GamePadState> src)
        {
            return src.Select(s => s.DPad);
        }

        public static IObservable<GamePadThumbSticks> ToSticks(this IObservable<GamePadState> src)
        {
            return src.Select(s => s.ThumbSticks);
        }

        public static IObservable<GamePadButtons> ToButtons(this IObservable<GamePadState> src)
        {
            return src.Select(s => s.Buttons);
        }

        public static IObservable<GamePadTriggers> ToTriggers(this IObservable<GamePadState> src)
        {
            return src.Select(s => s.Triggers);
        }

        // Use for controller
        public static IObservable<GamePadState> DoubleClick(this IObservable<GamePadState> src, int greatestTimeBetween)
        {
            return src.DistinctUntilChanged(f => f.PacketNumber).BufferWithTimeAndCount(TimeSpan.FromMilliseconds(greatestTimeBetween), 2);
        }

        public static IEnumerable<Buttons> GetPressedButtons(this GamePadState state)
        {
            var states = state.Buttons;
            if (states.A == ButtonState.Pressed) yield return Buttons.A;
            if (states.B == ButtonState.Pressed) yield return Buttons.B;
            if (states.Back == ButtonState.Pressed) yield return Buttons.Back;
            if (states.X == ButtonState.Pressed) yield return Buttons.X;
            if (states.Y == ButtonState.Pressed) yield return Buttons.Y;
            if (states.BigButton == ButtonState.Pressed) yield return Buttons.BigButton;
            if (states.LeftShoulder == ButtonState.Pressed) yield return Buttons.LeftShoulder;
            if (states.RightShoulder == ButtonState.Pressed) yield return Buttons.RightShoulder;
            if (states.Start == ButtonState.Pressed) yield return Buttons.Start;
            if (states.LeftStick == ButtonState.Pressed) yield return Buttons.LeftStick;
            if (states.RightStick == ButtonState.Pressed) yield return Buttons.RightStick;

            var dpadButtons = state.DPad;
            if (dpadButtons.Down == ButtonState.Pressed) yield return Buttons.DPadDown;
            if (dpadButtons.Up == ButtonState.Pressed) yield return Buttons.DPadUp;
            if (dpadButtons.Left == ButtonState.Pressed) yield return Buttons.DPadLeft;
            if (dpadButtons.Left == ButtonState.Pressed) yield return Buttons.DPadLeft;

            var triggersButtons = state.Triggers;
            if (triggersButtons.Left > 0) yield return Buttons.LeftTrigger;
            if (triggersButtons.Right > 0) yield return Buttons.RightTrigger;

            //var sticks = state.ThumbSticks;
        }

        public static bool ButtonsPressed(this GamePadState state, params Buttons[] buttons)
        {
            return state.GetPressedButtons().Join(buttons, o => o, i => i, (o, i) => i).Any();
        }

        public static bool ButtonsPressed(this GamePadState state)
        {
            return state.GetPressedButtons().Any();
        }

        public static IObservable<GamePadState> ButtonsHeld(this IObservable<GamePadState> src, HashSet<Buttons> buttons, int delay = Constants.DelayTime)
        {
            var down = src.Where(states => buttons.Overlaps(states.GetPressedButtons()));
            var up = src.Where(states => !buttons.Overlaps(states.GetPressedButtons())); // this may not be needed...

            return down.DelayEachUntil<GamePadState>(up, TimeSpan.FromMilliseconds(delay));
        }

        public static IObservable<GamePadState> ButtonsPressed(this IObservable<GamePadState> src, HashSet<Buttons> buttons)
        {
            return src.Where(state => buttons.Overlaps(state.GetPressedButtons()));
        }

        public static IObservable<GamePadState> ButtonHeld(this IObservable<GamePadState> src, Buttons button, int delayTime = RxInputManager.Constants.DelayTime)
        {
            var down = src.Where(state => state.IsButtonDown(button));
            var up = src.Where(state => state.IsButtonUp(button));

            return down.DelayEachUntil<GamePadState>(up, TimeSpan.FromMilliseconds(delayTime));
        }

        public static IObservable<GamePadState> ButtonReleased(this IObservable<GamePadState> src, Buttons button)
        {
            return src.Where(state => state.IsButtonDown(button))
                      .SkipWhile(state => state.IsButtonDown(button));
        }

        public static IObservable<GamePadState> ButtonPressed(this IObservable<GamePadState> src, Buttons button)
        {
            return src.Where(state => state.IsButtonDown(button));
        }

        private delegate bool CheckStickMethod(ref GamePadState state, int threshold);

        public static IObservable<GamePadState> Sticks(this IObservable<GamePadState> src, Buttons stick, int threshold)
        {
            //  if (stick != Buttons.LeftStick || stick != Buttons.RightStick)
            //    throw new KeyNotFoundException(String.Format("{0} is not the left or right stick", stick));

            CheckStickMethod check = (ref GamePadState g, int t) => false;
            if (stick == Buttons.LeftStick)
                check = CheckLeftStickThreshold;
            else if (stick == Buttons.RightStick)
                check = CheckRightStickThreshold;

            return src.Where(states => states.IsButtonUp(stick) && check(ref states, threshold));

        }

        private static int IntAbs(int x)
        {
            unchecked
            {
                return (x ^ (x >> 31)) - (x >> 31);
            }
        }

        private static int IntAbs(float x)
        {
            return IntAbs((int)(x * 1000.0f));
        }

        private static long LongAbs(long x)
        {
            unchecked
            {
                return (x ^ (x >> 63)) - (x >> 63);
            }
        }

        private static long LongAbs(double x)
        {
            return LongAbs((int)(x * 1000.0f));
        }

        private static bool CheckLeftStickThreshold(ref GamePadState sticks, int threshold)
        {
            return IntAbs(sticks.ThumbSticks.Left.X) > threshold || IntAbs(sticks.ThumbSticks.Left.Y) > threshold;
        }

        private static bool CheckRightStickThreshold(ref GamePadState sticks, int threshold)
        {
            return IntAbs(sticks.ThumbSticks.Right.X) > threshold || IntAbs(sticks.ThumbSticks.Right.Y) > threshold;
        }
    }
}
